//
//  DeviceSetupWindowController.m
//  MusicAnalyzer
//
//  Created by jiro on 12/03/24.
//  Copyright 2012 jiro music. All rights reserved.
//

#import "DeviceSetupWindowController.h"

#import "IOKit/IOKitLib.h"
#import "IOKit/IOCFPlugin.h"
#import "IOKit/usb/IOUSBLib.h"
#import "CoreFoundation/CoreFoundation.h"
#import "/usr/include/mach/mach_port.h"		//	why need? miss setting of framework?

#import "../../../Common/DeviceSystem.h"
#import "../../../Common/MidiSystem.h"
#import "AppController.h"

@implementation DeviceSetupWindowController

@synthesize appController;

- (void) windowDidLoad
{
	[ self scanPort: self ];
}

- (IBAction) scanPort: (id) sender
{
	id thread = [ [ NSThread alloc ] initWithTarget: self selector: @selector( scanUsbPortThreadFunction: ) object: nil ];
	[ thread start ];
}

- (IBAction) midiCheckBoxClicked: (id) sender
{
//	NSLog( @"- (IBAction) midiCheckBoxClicked: (id) sender, sender %@", [ sender class ] );
	const NSTableView* view = (NSTableView*) sender;
	int selectedRowIndex = [ view selectedRow ];
	std::vector< sc::DeviceInfo* >* deviceInfos = [ deviceDataSource deviceInfos ];

	//	check
	if ( selectedRowIndex < 0 || selectedRowIndex > deviceInfos->size() )
	{
		NSLog( @"selectedRowIndex = %d", selectedRowIndex );
		return;
	}

	//	flip flop UsbMidiDevice to and from UsbDevice
	sc::DeviceInfo* info = deviceInfos->at( selectedRowIndex );
	if ( info->GetType() == sc::DeviceInfo::Type_UsbMidiDevice )
	{
		sc::UsbDeviceInfo* inheritInfo = dynamic_cast< sc::UsbDeviceInfo* >( info );
		(*deviceInfos)[ selectedRowIndex ] = new sc::UsbDeviceInfo( * inheritInfo );
		delete info;
	}
	else if ( info->GetType() == sc::DeviceInfo::Type_UsbDevice ) 
	{
		sc::UsbDeviceInfo* inheritInfo = dynamic_cast< sc::UsbDeviceInfo* >( info );
		(*deviceInfos)[ selectedRowIndex ] = new sc::UsbMidiDeviceInfo( * inheritInfo );
		delete info;
	}
	else
	{
		NSAssert( NO, @"DeviceTableDataSource can only deal usb device." );
	}
	[ deviceDataSource reloadData ];
}

- (IBAction) deviceTableClicked: (id) sender
{
//	NSLog( @"- (IBAction) deviceTableClicked: (id) sender = %@", [ sender class ] );

	NSTableView* tableView = (NSTableView*) sender;
	int selectedRowIndex = [ tableView selectedRow ];
	@try
	{
		if ( -1 == selectedRowIndex )
		{
			@throw nil;
		}

		std::vector< sc::DeviceInfo* >* infos = [ deviceDataSource deviceInfos ];
		sc::DeviceInfo* info = infos->at( selectedRowIndex );
		if ( NSOnState == [ disblayOnlyMidiDeviceButton state ] )
		{
			info = [ deviceDataSource usbMidiDeviceInfoOrderAt: selectedRowIndex ];
		}
		else
		{
			info = infos->at( selectedRowIndex );
		}

		if ( ! info->IsMidiDevice() | ! info->IsUsbDevice() )
		{	
			@throw nil;
		}

		const sc::UsbDeviceInfo* selectedDeviceInfo = dynamic_cast< sc::UsbDeviceInfo* >( info );
		[ acvtiveDeviceNameLabel setStringValue: 
			[ NSString stringWithFormat: @"Active Device:    %s %s",
				selectedDeviceInfo->GetVendorName().c_str(),
				selectedDeviceInfo->GetProductName().c_str() ] ];
		if ( ( info != NULL ) and ( info != activeDeviceInfo ) )
		{
			if ( midiInterface0 != NULL )
			{
				[ self destroyMidiInterface ];
			}
			midiInterface0 = [ self createMidiInterface: info ];
		}
		activeDeviceInfo = info;
	}
	@catch( id e )
	{
		activeDeviceInfo = NULL;
		[ acvtiveDeviceNameLabel setStringValue: [ NSString stringWithString: @"No device is selected." ] ];
		return;
	}
}

- (IBAction) displayOnlyMidiDeviceButtonClicked: (id) sender
{
//	NSLog( @"- (IBAction) displayOnlyMidiDeviceButtonClicked: (id) sender = %@", [ sender class ] );

	NSButton* button = (NSButton*) sender;
	NSInteger state = [ button state ];
	const sc::DeviceInfo* info = activeDeviceInfo;	//	take shelter to re-select

	if ( state == NSOnState )
	{
		[ deviceDataSource setDisplayOnlyMidiDevice: YES ];
	}
	else
	{
		[ deviceDataSource setDisplayOnlyMidiDevice: NO ];
	}

	[ deviceDataSource reloadData ];

	if ( info != nil )
	{
		[ deviceDataSource selectRowOf: info ];
	}
	else
	{
		[ deviceDataSource deselectAll ];
	}
}

- (IBAction) pushOkButton: (id) sender
{
	//	return usb midi device & updated device infos
	[ appController setDeviceInfos: [ deviceDataSource deviceInfos ] ];
	[ appController setMidiInterface: midiInterface0 ]; 
	[ [ self window ] close ];
}

- (IBAction) selectMidiParseOption: (id) sender
{
	NSLog( @"- (IBAction) selectMidiParseOption: (id) sender, %d", [ sender indexOfSelectedItem ] );
	NSComboBox* combo = (NSComboBox*) sender;
	switch ( [ combo indexOfSelectedItem ] )
	{	
	case 1:
		midiInterface0->SetParseOption( sc::MidiParser::Option_One );
	case 0:
	default:
		midiInterface0->SetParseOption( sc::MidiParser::Option_None );
	}
}

- (void) scanUsbPortThreadFunction: (id) arg
{
//	NSLog( @"- (void) scanPortThreadFunction: (id) arg" );

	//	define variable
	mach_port_t masterPort = 0;
	CFMutableDictionaryRef matchingDict;
	kern_return_t kernelReturn;
	io_iterator_t deviceIterator;

	//	create usb midi device list
	std::vector< sc::UsbMidiDeviceInfo > usbMidiDevices;
	const std::vector< sc::DeviceInfo* >* infos = [ deviceDataSource deviceInfos ];
	for ( int i = 0, count = infos->size(); i < count; i++ )
	{

		const sc::DeviceInfo* info = infos->at( i );
		if (  sc::DeviceInfo::Type_UsbMidiDevice != info->GetType() )
		{
			continue;
		}
		const sc::UsbMidiDeviceInfo* usbMidiDeviceInfo = dynamic_cast< const sc::UsbMidiDeviceInfo* >( info ) ;
		sc::UsbMidiDeviceInfo usbMidi( * usbMidiDeviceInfo );
		usbMidiDevices.push_back( usbMidi );
	}

	[ deviceDataSource clearDeviceInfos ];

	@try
	{
		kernelReturn = IOMasterPort( MACH_PORT_NULL, & masterPort );
		if ( err_none != kernelReturn )
		{
			id s = [ NSString stringWithFormat: @"IOMasterPort( MACH_PORT_NULL, & masterPort ) fail, %08x", kernelReturn ];
			@throw ( s );
		}

		//	set up matching dictionary
		matchingDict = IOServiceMatching( kIOUSBDeviceClassName );
		if ( NULL == matchingDict )
		{
			id s = [ NSString stringWithFormat: @"IOServiceMatching( kIOUSBDeviceClassName ) fail, %08x", kernelReturn ];
			@throw ( s );
		}

		//	set dictionary value, almost MIDI device has following BaesClass, SubClass, Pritocol value.
		const int usbDeviceClass = 0x00;
		const int usbDeviceSubClass = 0x00;
		const int usbDeviceProtocol = 0x00;
		CFDictionarySetValue(
			matchingDict, CFSTR( kUSBDeviceClass ), CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, & usbDeviceClass ) );
		CFDictionarySetValue( 
			matchingDict, CFSTR( kUSBDeviceSubClass), CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, & usbDeviceSubClass ) );
		CFDictionarySetValue( 
			matchingDict, CFSTR( kUSBDeviceProtocol ), CFNumberCreate( kCFAllocatorDefault, kCFNumberSInt32Type, & usbDeviceProtocol ) );
		kernelReturn = IOServiceGetMatchingServices( masterPort, matchingDict, & deviceIterator );
		if ( err_none != kernelReturn )
		{
			id s = [ NSString stringWithFormat: 
				@"IOServiceGetMatchingServices( masterPort, matchingDict, & deviceIterator ) fail, %08x",
				kernelReturn ];
			@throw ( s );
		}
		//CFRetain( matchingDict );

		mach_port_t service;
		while ( service = IOIteratorNext( deviceIterator ) )
		{
//			NSLog( @"device examing loop" );	

			//	get device's vendor/product id/name
			CFTypeRef typeRef = 0;
			uint16 vendorId = 0;
			uint16 productId = 0;
			const char* vendorName = 0;
			const char* productName = 0;

			typeRef = IORegistryEntryCreateCFProperty( service, CFSTR( kUSBVendorID ), kCFAllocatorDefault, 0x00 );			
			CFNumberGetValue( (CFNumberRef) typeRef, kCFNumberSInt16Type, & vendorId );
			CFRelease( typeRef );

			typeRef = IORegistryEntryCreateCFProperty( service, CFSTR( kUSBProductID ), kCFAllocatorDefault, 0x00 );
			CFNumberGetValue( (CFNumberRef) typeRef, kCFNumberSInt16Type, & productId );
			CFRelease( typeRef );

			typeRef = IORegistryEntryCreateCFProperty( service, CFSTR( kUSBVendorString ), kCFAllocatorDefault, 0x00 );
			vendorName = CFStringGetCStringPtr( ( CFStringRef ) typeRef, kCFStringEncodingUTF8 );
			CFRelease( typeRef );

			typeRef = IORegistryEntryCreateCFProperty( service, CFSTR( kUSBProductString ), kCFAllocatorDefault, 0x00 );
			productName = CFStringGetCStringPtr( (CFStringRef) typeRef, kCFStringEncodingUTF8 );
			CFRelease( typeRef );
			
			//mach_port_deallocate( mach_task_self(), service );
			IOObjectRelease( service );
			BOOL isMidiDevice = NO;
			for ( size_t i = 0, count = usbMidiDevices.size(); i < count; i++ )
			{
				const sc::UsbMidiDeviceInfo& info = usbMidiDevices[ i ];
				if ( ( info.GetVendorId() == vendorId ) && ( info.GetProductId() == productId ) )
				{
					isMidiDevice = YES;
					break;
				}
			}
			sc::DeviceInfo* newInfo;
			sc::UsbMidiDeviceInfo usbMidiDeviceInfo( vendorId, productId, vendorName, productName );
			sc::UsbDeviceInfo ubsDeviceInfo( vendorId, productId, vendorName, productName );
			if ( isMidiDevice )
			{
				newInfo = & usbMidiDeviceInfo;
			}
			else
			{
				newInfo = & ubsDeviceInfo;
			}
			[ deviceDataSource addDeviceInfo: newInfo ];
//			NSLog( @"%08x, %s", newInfo, (char*) newInfo->ToString().c_str() );
		}
	}
	@catch ( id e )
	{
		NSLog( @"error: %@", e );
	}
	@finally
	{
		//	post process
		if ( 0 != masterPort )
		{
			mach_port_deallocate( mach_task_self(), masterPort );
			masterPort = NULL;
		}
	}
}

- (void) setDeviceInfos: ( const std::vector< sc::DeviceInfo* >* ) infos
{
	[ deviceDataSource addDeviceInfos: infos ];
}

- (sc::MidiInterface< 0 >*) createMidiInterface: ( const sc::DeviceInfo* ) info;
{
	sc::MidiInterface< 0 >::CreateInstance( * info );
	sc::MidiInterface< 0 >* instance = sc::MidiInterface< 0 >::GetInstance();
	return instance;
}

- (void) destroyMidiInterface
{
	sc::MidiInterface< 0 >::DestroyInstance();
}

@end
